/**
 * 360浏览器书签解码工具 - Node.js实现
 *
 * 此脚本用于解码360浏览器的书签文件
 * 需要安装以下依赖:
 * npm install winreg fs
 */

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const WinReg = require('winreg');
const { Buffer } = require('buffer');
const { execSync } = require('child_process');

// 辅助函数，模拟Python中的c_uint32
function uint32(value) {
  return value >>> 0; // 无符号32位整数
}

// 模拟Python中的辅助函数
function _ZIMU(d1, d2) {
  // 使用BigInt进行精确乘法
  const result = BigInt(d1) * BigInt(d2);
  // 转换回Number类型并确保是32位无符号整数
  return uint32(Number(result & BigInt(0xFFFFFFFF)));
}

function _ZOO3(d1, d2) {
  // 转换为BigInt
  const b1 = BigInt(uint32(d1));
  const b2 = BigInt(uint32(d2));

  // 使用BigInt进行位移操作
  const shifted = b1 >> BigInt(16);

  // 调用_ZIMU函数，它已经支持BigInt
  return _ZIMU(Number(b2), Number(shifted));
}

function _ZOO1(d1, d2, d3) {
  // 使用BigInt进行精确计算
  const product = BigInt(_ZIMU(d2, d1));
  const zoo3Result = BigInt(_ZOO3(d1, d3));

  // 执行减法操作
  const result = product - zoo3Result;

  // 转换回Number类型并确保是32位无符号整数
  return uint32(Number(result & BigInt(0xFFFFFFFF)));
}

function _ZOO2(d1, d2, d3) {
  // 使用BigInt进行精确计算
  const product = BigInt(_ZIMU(d2, d1));
  const zoo3Result = BigInt(_ZOO3(d1, d3));

  // 执行加法操作
  const result = product + zoo3Result;

  // 转换回Number类型并确保是32位无符号整数
  return uint32(Number(result & BigInt(0xFFFFFFFF)));
}

function bigIntOr(a, b) {
  // 将数字转换为二进制字符串
  const binA = a.toString(2);
  const binB = b.toString(2);
  // 确保两个二进制字符串长度相同
  const maxLen = Math.max(binA.length, binB.length);
  const paddedA = binA.padStart(maxLen, '0');
  const paddedB = binB.padStart(maxLen, '0');
  // 逐位进行按位或运算
  let result = '';
  for (let i = 0; i < maxLen; i++) {
    result += (paddedA[i] === '1' || paddedB[i] === '1') ? '1' : '0';
  }
  return uint32(Number(BigInt('0b' + result)));
}

function bigIntXOR(a, b) {
  // 将 BigInt 转换为二进制字符串
  const binA = a.toString(2);
  const binB = b.toString(2);
  // 补齐长度以对齐二进制位
  const maxLen = Math.max(binA.length, binB.length);
  const paddedA = binA.padStart(maxLen, '0');
  const paddedB = binB.padStart(maxLen, '0');
  // 逐位异或
  let result = '';
  for (let i = 0; i < maxLen; i++) {
    result += paddedA[i] === paddedB[i] ? '0' : '1';
  }
  // 转回 BigInt
  return uint32(Number(BigInt('0b' + result)));
}

// CS64_WordSwap函数
function CS64_WordSwap(pSrcStr, iChNum, pdwMd5, pOutInt) {
  if (iChNum < 2 || iChNum & 1) {
    return false;
  }

  const dwMD5_0 = uint32((pdwMd5[0] | 1) + 0x69FB0000);
  const dwMD5_1 = uint32((pdwMd5[1] | 1) + 0x13DB0000);
  let dwRet1 = 0;
  let dwRet0 = 0;
  let i = 0;
  while (i < iChNum) {
    let dwTemp00 = dwRet0;
    if (i < iChNum) {
      dwTemp00 += pSrcStr[i];
    }
    dwTemp00 = uint32(dwTemp00);

    const dwTemp01 = _ZOO1(dwTemp00, dwMD5_0, 0x10FA9605);
    const dwTemp02 = _ZOO2(dwTemp01, 0x79F8A395, 0x689B6B9F);
    const dwTemp03 = _ZOO1(dwTemp02, 0xEA970001, 0x3C101569);
    i++;

    let dwTemp04 = dwTemp03;
    if (i < iChNum) {
      dwTemp04 += pSrcStr[i];
    }
    dwTemp04 = uint32(dwTemp04);

    const dwTemp05 = _ZOO1(dwTemp04, dwMD5_1, 0x3CE8EC25);
    const dwTemp06 = _ZOO1(dwTemp05, 0x59C3AF2D, 0x2232E0F1);
    const dwTemp07 = _ZOO2(dwTemp06, 0x1EC90001, 0x35BD1EC9);
    i++;

    dwRet0 = dwTemp07;
    dwRet1 = uint32(dwTemp03 + dwRet0 + dwRet1);
  }

  pOutInt[0] = dwRet0;
  pOutInt[1] = dwRet1;
  console.log('CS64_WordSwap pOutInt:', pOutInt.map(x => '0x' + x.toString(16)).join(','));
  return true;
}

// CS64_Reversible函数
function CS64_Reversible(pSrcStr, iChNum, pdwMd5, pOutInt) {
  if (iChNum < 2 || iChNum & 1) {
    return false;
  }

  let dwRet1 = 0;
  let dwRet0 = 0;
  const dwMD5_0 = bigIntOr(pdwMd5[0], 1n);
  const dwMD5_1 = bigIntOr(pdwMd5[1], 1n);
  let i = 0;

  while (i < iChNum) {
    let dwTemp00 = dwRet0;
    if (i < iChNum) {
      dwTemp00 += pSrcStr[i];
    }
    dwTemp00 = uint32(dwTemp00);

    const dwTemp01 = _ZIMU(dwMD5_0, dwTemp00);
    const dwTemp02 = _ZOO1(dwTemp01, 0xB1110000, 0x30674EEF);
    const dwTemp03 = _ZOO1(dwTemp02, 0x5B9F0000, 0x78F7A461);
    const dwTemp04 = _ZOO2(dwTemp03, 0xB96D0000, 0x12CEB96D);
    const dwTemp05 = _ZOO2(dwTemp04, 0x1D830000, 0x257E1D83);
    i++;

    let dwTemp06 = dwTemp05;
    if (i < iChNum) {
      dwTemp06 += pSrcStr[i];
    }
    dwTemp06 = uint32(dwTemp06);

    const dwTemp07 = _ZIMU(dwMD5_1, dwTemp06);
    const dwTemp08 = _ZOO1(dwTemp07, 0x16F50000, 0x5D8BE90B);
    const dwTemp09 = _ZOO1(dwTemp08, 0x96FF0000, 0x2C7C6901);
    const dwTemp10 = _ZOO2(dwTemp09, 0x2B890000, 0x7C932B89);
    const dwTemp11 = _ZOO1(dwTemp10, 0x9F690000, 0x405B6097);
    i++;

    dwRet0 = dwTemp11;
    dwRet1 = uint32(dwTemp05 + dwRet0 + dwRet1);
  }

  pOutInt[0] = dwRet0;
  pOutInt[1] = dwRet1;
  console.log('CS64_Reversible pOutInt:', pOutInt.map(x => '0x' + x.toString(16)).join(','));
  return true;
}

// BuildPatentHash_函数
function BuildPatentHash_(pWsStr, pMd5, pOut) {
  const dwWsLen = pWsStr.length;
  let dwCount = Math.floor(dwWsLen / 4);
  if (dwCount & 1) {
    dwCount -= 1;
  }

  const r1 = [0, 0];
  const r2 = [0, 0];
  const plist = [];

  for (let i = 0; i < dwCount; i++) {
    plist.push(pWsStr.readUInt32LE(i * 4));
  }

  if (!CS64_WordSwap(plist, dwCount, pMd5, r1) || !CS64_Reversible(plist, dwCount, pMd5, r2)) {
    return false;
  }

  for (let i = 0; i < r1.length; i++) {
    pOut.push(bigIntXOR(r1[i], r2[i]));
  }

  return true;
}

// 获取MD5哈希
function getmd5hex_bs(bs) {
  const md5 = crypto.createHash('md5');
  md5.update(bs);
  const digest = md5.digest();
  console.log(digest.toString('hex'));
  return digest;
}

// MTEA加密
function mteaencrypt(v, k) {
  let v0 = uint32(v[0]);
  let v1 = uint32(v[1]);
  const delta = uint32(0x61C88647);
  const k0 = uint32(k[0]), k1 = uint32(k[1]), k2 = uint32(k[2]), k3 = uint32(k[3]);

  let total = uint32(0x9E3779B9);
  for (let i = 0; i < 8; i++) {
    v0 = uint32(v0 + uint32(((v1 << 4) + k0) ^ uint32(v1 + total) ^ uint32((v1 >>> 5) + k1)));
    v1 = uint32(v1 + uint32(((v0 << 4) + k2) ^ uint32(v0 + total) ^ uint32((v0 >>> 5) + k3)));
    total = uint32(total - delta);
  }

  return [v0, v1];
}

// MTEA解密
function mteadecrypt(v, k) {
  let v0 = uint32(v[0]);
  let v1 = uint32(v[1]);
  const delta = uint32(0x61C88647);
  const k0 = uint32(k[0]), k1 = uint32(k[1]), k2 = uint32(k[2]), k3 = uint32(k[3]);

  let total = uint32(0x8ff34781);
  for (let i = 0; i < 8; i++) {
    total = uint32(total + delta);
    v1 = uint32(v1 - uint32(((v0 << 4) + k2) ^ uint32(v0 + total) ^ uint32((v0 >>> 5) + k3)));
    v0 = uint32(v0 - uint32(((v1 << 4) + k0) ^ uint32(v1 + total) ^ uint32((v1 >>> 5) + k1)));
  }

  return [v0, v1];
}

// TEA360加密
function tea360(databs, size = 0x80) {
  const dsz = databs.length;
  if (dsz % 4 !== 0 || size % 4 !== 0) {
    console.log('len error!');
    return null;
  }

  let buffer = Buffer.from(databs);
  if (size > dsz) {
    const padding = Buffer.alloc(size - dsz, 0);
    buffer = Buffer.concat([buffer, padding]);
  }

  const idatas = [];
  const keys = [];
  for (let i = 0; i < buffer.length; i += 4) {
    const inum = buffer.readUInt32LE(i);
    if (i < 16) {
      keys.push(inum);
    }
    idatas.push(inum);
  }

  const result = Buffer.alloc(buffer.length);
  for (let j = 0; j < idatas.length; j += 2) {
    if (j + 1 < idatas.length) { // 确保有两个元素可用
      const [x, y] = mteaencrypt([idatas[j], idatas[j + 1]], keys);
      result.writeUInt32LE(uint32(x), j * 4);
      result.writeUInt32LE(uint32(y), (j + 1) * 4);
    }
  }

  return result;
}

// 获取Unicode字节
function getUnicode_bs(s) {
  const buffer = Buffer.alloc(s.length * 2);
  for (let i = 0; i < s.length; i++) {
    buffer.writeUInt16LE(s.charCodeAt(i), i * 2);
  }
  return buffer;
}

// 获取MachineGuid
async function getMID() {
  return new Promise((resolve, reject) => {
    const regKey = new WinReg({
      hive: WinReg.HKLM,
      key: '\\SOFTWARE\\Microsoft\\Cryptography'
    });

    regKey.get('MachineGuid', (err, item) => {
      if (err) {
        console.error('Error getting MachineGuid:', err);
        resolve('');
        return;
      }

      if (item && item.value) {
        console.log('MachineGuidstr:', item.value);
        resolve(item.value);
      } else {
        console.error('MachineGuid not found');
        resolve('');
      }
    });
  });
}

// 获取SID
async function getSID() {
  try {
    // 使用wmic命令获取当前用户的SID
    const username = process.env.USERNAME;
    const command = `wmic useraccount where name='${username}' get sid`;
    const result = execSync(command, { encoding: 'utf8' });

    // 解析输出获取SID
    const lines = result.trim().split('\n');
    if (lines.length >= 2) {
      const sid = lines[1].trim();
      console.log(username, sid);
      return sid;
    }

    throw new Error('无法解析SID');
  } catch (error) {
    console.error('Error getting SID:', error);
    return '';
  }
}

// 随机数生成
function rand(myseed) {
  const n = myseed * 0x343fd + 0x269ec3;
  return n & 0xffffffff;
}

// 随机加密
function randenc(data, seed = 0x8000402B) {
  const out = Buffer.alloc(data.length + 4);
  out.writeUInt32LE(seed, 0);

  const sz = data.length;
  const n = sz % 4;
  const m = n ? Math.floor(sz / 4) * 4 : sz;

  let currentSeed = seed;
  for (let i = 0; i < m; i += 4) {
    currentSeed = rand(currentSeed);
    const bs = Buffer.alloc(4);
    bs.writeUInt32LE(uint32(currentSeed), 0);
    for (let j = 0; j < 4; j++) {
      out[4 + i + j] = data[i + j] ^ bs[j];
    }
  }

  if (n) {
    currentSeed = rand(currentSeed);
    const bs = Buffer.alloc(4);
    bs.writeUInt32LE(uint32(currentSeed), 0);

    for (let i = 0; i < n; i++) {
      out[4 + m + i] = data[m + i] ^ bs[i];
    }
  }

  return out;
}

// URL编码
function urlenc(bs) {
  const unreservedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.:/"
  let ret = Buffer.alloc(bs.length * 3); // 最坏情况下每个字节都需要3个字符
  let pos = 0;

  for (let i = 0; i < bs.length; i++) {
    const x = bs[i];
    const char = String.fromCharCode(x);

    if (unreservedChars.includes(char)) {
      ret[pos++] = x;
    } else if (char === ' ') {
      ret[pos++] = 43; // '+'
    } else {
      const hex = x.toString(16).padStart(2, '0');
      ret[pos++] = 37; // '%'
      ret[pos++] = hex.charCodeAt(0);
      ret[pos++] = hex.charCodeAt(1);
    }
  }

  return ret.slice(0, pos);
}

// 解码360书签的主函数
async function decode360Bookmarks() {
  // 定义stub数据
  const stub = Buffer.from([
    0x1F, 0x7D, 0x14, 0x89, 0x4D, 0xB8, 0x8B, 0x4D, 0x18, 0x89, 0x45, 0xBC, 0x89, 0x7D, 0xB0, 0x89,
    0x4D, 0xAC, 0x89, 0x5D, 0xD0, 0x89, 0x5D, 0xC8, 0x88, 0x5D, 0xD7, 0x88, 0x5D, 0xC0, 0x3B, 0xFB,
    0x0F, 0x84, 0x77, 0x93, 0x03, 0x00, 0x89, 0x90, 0x8B, 0x4E, 0x10, 0xF7, 0x41, 0x08, 0x00, 0x40,
    0x00, 0x00, 0x0F, 0x84, 0x6F, 0x93, 0x03, 0x00, 0xF7, 0x46, 0x68, 0x00, 0x01, 0x00, 0x00, 0x0F,
    0x85, 0x58, 0xCC, 0x03, 0x00, 0xF6, 0x05, 0xA0, 0x03, 0xFE, 0x7F, 0x01, 0xBF, 0x00, 0x01, 0x00,
    0x02, 0x0F, 0x85, 0x4F, 0x96, 0x03, 0x00, 0x89, 0x5D, 0xD8, 0x85, 0x7E, 0x68, 0x0F, 0x85, 0x8B,
    0x96, 0x03, 0x00, 0x38, 0x5E, 0x02, 0x0F, 0x85, 0xEA, 0x96, 0xCC, 0x00, 0xEB, 0x1D, 0x39, 0x5D,
    0xC8, 0x0F, 0x85, 0x3B, 0x97, 0x03, 0x00, 0x8B, 0x45, 0xD8, 0x8B, 0x4D, 0xFC, 0x5F, 0x5E, 0x33,
    0x10, 0x00, 0x00, 0x00, 0x57, 0x00, 0x44, 0x00, 0x4C, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00,
    0x36, 0x00, 0x00, 0x00
  ]);

  // 获取SID和MID
  const sid = await getSID();
  const mid = await getMID();

  // 构建ploadone
  let ploadone = Buffer.concat([
    stub,
    getUnicode_bs(sid),
    getUnicode_bs('/?'),
    getUnicode_bs(mid)
  ]);

  console.log('ploadone:', ploadone.toString('hex'));

  // 计算MD5
  const md5ploadone_bs = getmd5hex_bs(ploadone);

  const a = md5ploadone_bs.readUInt32LE(0);
  console.log('a:', '0x' + a.toString(16));

  const b = md5ploadone_bs.readUInt32LE(4);
  console.log('b:', '0x' + b.toString(16));

  const imd5 = [a, b];
  const pout = [];

  // 构建专利哈希
  BuildPatentHash_(ploadone, imd5, pout);
  let mhashbs = Buffer.alloc(8);
  mhashbs.writeUInt32LE(uint32(pout[0]), 0);
  mhashbs.writeUInt32LE(uint32(pout[1]), 4);

  // URL编码
  const mhash_encbs = urlenc(Buffer.from(mhashbs.toString('base64')));

  // 构建ploadtwo
  const buf1 = Buffer.alloc(4);
  buf1.writeUInt32LE(1, 0);
  const buf2 = Buffer.alloc(4);
  buf2.writeUInt32LE(stub.length, 0);
  const buf3 = Buffer.alloc(4);
  buf3.writeUInt32LE(2, 0);
  const buf4 = Buffer.alloc(4);
  buf4.writeUInt32LE(mhash_encbs.length, 0);
  const ploadtwo = Buffer.concat([ buf1, buf2, stub, buf3, buf4, mhash_encbs ]);

  // 随机加密
  const randenc_bs = randenc(ploadtwo);
  console.log('[#]gen p1');

  // 计算MD5并进行TEA加密
  let tmp_bs = getmd5hex_bs(randenc_bs);
  tmp_bs = tea360(Buffer.from(tmp_bs.toString('hex')));

  // 填充
  const padSize = 0xc0 + 1;
  const padLength = padSize - tmp_bs.length;
  const padding = Buffer.alloc(padLength, 0, 'hex');
  tmp_bs = Buffer.concat([tmp_bs, padding]).slice(0, -1);

  console.log('\n#######################################################################################################');
  console.log('[#]360History sqlite db key:');
  // 计算最终密钥
  const key_bs = getmd5hex_bs(tmp_bs).toString('hex');
  console.log('#######################################################################################################\n');

  // 解密书签
  try {
    // 书签文件路径
    const filePath = path.join(process.env.APPDATA, 'secoresdk', '360se6', 'User Data', 'Default', '360Bookmarks');
    // 读取书签文件
    const bookmark = fs.readFileSync(filePath);

    // 解码Base64
    const bookmarkData = Buffer.from(bookmark.toString(), 'base64').slice(4);

    // 创建AES解密器
    const decipher = crypto.createDecipheriv(
      'aes-256-cbc',
      key_bs, // 直接使用MD5哈希作为密钥，不需要再转换
      Buffer.from('33mhsq0uwgzblwdo')
    );

    // 解密
    let decrypted = decipher.update(bookmarkData);
    decrypted = Buffer.concat([decrypted, decipher.final()]);

    console.log(decrypted.toString());

    // 保存到文件
    fs.writeFileSync('bookmark.txt', decrypted);
    console.log('书签已保存到 bookmark.txt');

    return decrypted.toString();
  } catch (error) {
    console.error('解密书签时出错:', error);
    return null;
  }
}

// 执行主函数
decode360Bookmarks().catch(console.error);
